/*
 *
 *   Copyright (C) 2002-2005 by Raymond Huang
 *   plushuang at users.sourceforge.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU Lesser General Public License in all respects
 *  for all of the code used other than OpenSSL.  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so.  If you
 *  do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include <string.h>

#include <urlglib/root_node.h>
#include <urlglib/conf_parser.h>
#include <urlglib/conf_writer.h>
#include <urlglib/ug_i18n.h>
#include <urlglib/html_import.h>
#include <urlglib/url_filter.h>

#include "filter_importer.h"
#include "queue_ctrl.h"

#include "main_app.h"
#include "main_app_callback.h"
#include "urlgfe_util.h"

#define URLGFE_CONF_TAG    "urlgfe_conf"

// used in main_app_init ()
// compare function for g_tree_new ()
static int _ptr_key_compare (const void* a, const void* b)
{
	return (char*)a - (char*)b;
}

// used in main_app_finalize ()
// traverse function for g_tree_foreach ()
static gboolean queue_ctrl_tree_func (gpointer key, gpointer value, gpointer data)
{
	queue_ctrl_unref (value);
	return FALSE;
}

// used in main_app_get_selected_download ()
// for gtk_tree_selection_selected_foreach ()
static void selection_get_each (GtkTreeModel *model, GtkTreePath *path,
                                GtkTreeIter *iter, gpointer data)
{
	DownloadNode* dnode;
	GList** list = (GList**)data;

	gtk_tree_model_get (model, iter, 0, &dnode, -1);
	*list = g_list_prepend (*list, dnode);
}

// used in main_app_get_selected_download ()
// for gtk_clipboard_request_text ()
static void clipboard_text_received_callback (GtkClipboard *clipboard,
                                              const gchar *text,
                                              gpointer data)
{
	MainApp* app = (MainApp*)data;
	gchar*   clipboard_text;

	if (text == NULL) {
		g_free (app->clipboard_text);
		app->clipboard_text = NULL;
	} else {
		clipboard_text = (app->clipboard_text) ? app->clipboard_text : "";
		if (g_ascii_strcasecmp (text, clipboard_text) != 0) {
			g_free (app->clipboard_text);
			app->clipboard_text = g_strdup (text);
			main_app_import_text (app, app->clipboard_text, TRUE);
		}
	}

	app->clipboard_processing = FALSE;
}

static gboolean main_app_on_timer_save (MainApp* app)
{
	main_app_save_data (app);
	return TRUE;
}

// controled by main_app_monitor_clipboard () and main_app_job_changed ()
// for g_timeout_add_full ()
static gboolean main_app_on_timer (MainApp* app)
{
	MsgServer* msg_server = app->msg_server;

	app->timer_count++;

	// check clipboard
	if (app->clipboard_monitor && app->clipboard_processing == FALSE) {
		app->clipboard_processing = TRUE;
		gtk_clipboard_request_text (app->clipboard, clipboard_text_received_callback, app);
	}

	// check message from other instance.
	if (msg_server) {
		msg_server_lock (msg_server);
		if (msg_server->url_list) {
			main_app_import_text (app, msg_server->url_list->data, FALSE);
			g_free (msg_server->url_list->data);
			msg_server->url_list = g_list_delete_link (msg_server->url_list, msg_server->url_list);
		}
		msg_server_unlock (msg_server);
	}

	// refresh & queuing every 2 count.
	if (app->timer_count & 1)
		return TRUE;

	// if no workable job in queue, rest.
	if (app->queuing_job==FALSE)
		return TRUE;

	root_node_refresh (app->rnode);
	main_window_message_area_refresh (&app->window);
//	g_message ("act category_1 %d", g_list_length (app->rnode->active_list));

	// start queuing
//	g_message ("app->queuing %d", app->queuing);
	if (app->queuing)
		root_node_start (app->rnode);

	if (app->window.queue_ctrl)
		gtk_widget_queue_draw (GTK_WIDGET (app->window.queue_ctrl->view));
	gtk_widget_queue_draw (GTK_WIDGET (app->window.category_view));

	// if no workable job in queue, rest.
	if (root_node_is_active (app->rnode) == FALSE)
		app->queuing_job = FALSE;
//	g_message ("act category_2 %d", g_list_length (app->rnode->active_list));
	return TRUE;
}

void main_app_init (MainApp* app)
{
	CategoryNode* cnode;
	const char* err_str;
	int   err_len;

	app->queue_ctrl_tree = g_tree_new (_ptr_key_compare);
	app->rnode = root_node_new ();
	app->model = category_tree_model_new (app->rnode);
	app->timer_count = 0;
	// message server
	app->msg_server = msg_server_new ();
	// clipboard data
	app->clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
	app->clipboard_text = NULL;
	app->clipboard_monitor = TRUE;
	app->clipboard_processing = FALSE;
	app->clipboard_pattern = NULL;
	app->queuing = TRUE;
	app->queuing_job = FALSE;

	main_window_init (&app->window);
	gtk_tree_view_set_model (app->window.category_view, GTK_TREE_MODEL (app->model));

	main_app_load_conf (app);
	main_app_load_data (app);
	if (root_node_first_category (app->rnode) == NULL) {
		cnode = category_node_new ();
		root_node_append (app->rnode, cnode);
		category_node_set_name (cnode, "Home");
		download_node_set_directory (cnode->download_default, g_get_home_dir ());
		gtk_tree_view_expand_all (app->window.category_view);
	}
	if (app->clipboard_pattern == NULL)
		app->clipboard_pattern = g_strdup ("ZIP|BIN|GZ|Z|TAR|TGZ|BZ2|A[0-9]?|LZH|MP3|RPM|DEB|EXE|RAR|R[0-9][0-9]");

	// regexp
	app->pcre_regexp = pcre_compile (app->clipboard_pattern, PCRE_CASELESS | PCRE_UTF8,
	                                 &err_str, &err_len, NULL);

	// window signal
	g_signal_connect (app->window.self, "delete-event",
	                  G_CALLBACK (main_app_on_delete_event), app);
	g_signal_connect (app->window.self, "destroy",
	                  G_CALLBACK (gtk_main_quit), NULL);
	g_signal_connect (app->window.category_view, "key-press-event",
	                  G_CALLBACK (main_app_on_key_press_event), app);

	// file menu signal
	g_signal_connect (app->window.menu.file.import_html, "activate",
	                  G_CALLBACK (main_app_on_import_html), app);
	g_signal_connect (app->window.menu.file.import_url, "activate",
	                  G_CALLBACK (main_app_on_import_url), app);
	g_signal_connect (app->window.menu.file.export_url, "activate",
	                  G_CALLBACK (main_app_on_export_url), app);
	g_signal_connect (app->window.menu.file.save_all, "activate",
	                  G_CALLBACK (main_app_on_save_all), app);
	g_signal_connect (app->window.menu.file.quit, "activate",
	                  G_CALLBACK (gtk_main_quit), NULL);


	// category signal -----------------------------------------------
	g_signal_connect (app->window.category_view, "cursor-changed",
	                  G_CALLBACK (main_app_on_category_selected), app);
	// category tool button
	g_signal_connect (app->window.toolbar.category_create, "clicked",
	                  G_CALLBACK (main_app_on_category_new), app);
	g_signal_connect (app->window.toolbar.category_erase, "clicked",
	                  G_CALLBACK (main_app_on_category_erase), app);

	// category menu
	g_signal_connect (app->window.menu.category.create, "activate",
	                  G_CALLBACK (main_app_on_category_new), app);
	g_signal_connect (app->window.menu.category.erase, "activate",
	                  G_CALLBACK (main_app_on_category_erase), app);
	g_signal_connect (app->window.menu.category.properties, "activate",
	                  G_CALLBACK (main_app_on_category_prop), app);
	g_signal_connect (app->window.menu.category.set_default, "activate",
	                  G_CALLBACK (main_app_on_category_default), app);

	// download signal -----------------------------------------------
	// download tool button signal
	g_signal_connect (app->window.toolbar.create, "clicked",
	                  G_CALLBACK (main_app_on_download_new), app);
	g_signal_connect (app->window.toolbar.create_batch, "clicked",
	                  G_CALLBACK (main_app_on_download_batch), app);
	g_signal_connect (app->window.toolbar.erase, "clicked",
	                  G_CALLBACK (main_app_on_download_erase), app);
	g_signal_connect (app->window.toolbar.start, "clicked",
	                  G_CALLBACK (main_app_on_download_start), app);
	g_signal_connect (app->window.toolbar.stop, "clicked",
	                  G_CALLBACK (main_app_on_download_stop), app);
	g_signal_connect (app->window.toolbar.move_up, "clicked",
	                  G_CALLBACK (main_app_on_download_move_up), app);
	g_signal_connect (app->window.toolbar.move_down, "clicked",
	                  G_CALLBACK (main_app_on_download_move_down), app);
	g_signal_connect (app->window.toolbar.move_top, "clicked",
	                  G_CALLBACK (main_app_on_download_move_top), app);
	g_signal_connect (app->window.toolbar.move_bottom, "clicked",
	                  G_CALLBACK (main_app_on_download_move_bottom), app);
	g_signal_connect (app->window.toolbar.property, "clicked",
	                  G_CALLBACK (main_app_on_download_prop), app);

	// download menu signal
	g_signal_connect (app->window.menu.download.create, "activate",
	                  G_CALLBACK (main_app_on_download_new), app);
	g_signal_connect (app->window.menu.download.create_batch, "activate",
	                  G_CALLBACK (main_app_on_download_batch), app);
	g_signal_connect (app->window.menu.download.erase, "activate",
	                  G_CALLBACK (main_app_on_download_erase), app);
	g_signal_connect (app->window.menu.download.start, "activate",
	                  G_CALLBACK (main_app_on_download_start), app);
	g_signal_connect (app->window.menu.download.stop, "activate",
	                  G_CALLBACK (main_app_on_download_stop), app);
	g_signal_connect (app->window.menu.download.move_to, "activate",
	                  G_CALLBACK (main_app_on_download_move_to), app);
	g_signal_connect (app->window.menu.download.move_up, "activate",
	                  G_CALLBACK (main_app_on_download_move_up), app);
	g_signal_connect (app->window.menu.download.move_down, "activate",
	                  G_CALLBACK (main_app_on_download_move_down), app);
	g_signal_connect (app->window.menu.download.move_top, "activate",
	                  G_CALLBACK (main_app_on_download_move_top), app);
	g_signal_connect (app->window.menu.download.move_bottom, "activate",
	                  G_CALLBACK (main_app_on_download_move_bottom), app);
	g_signal_connect (app->window.menu.download.properties, "activate",
	                  G_CALLBACK (main_app_on_download_prop), app);

	// Global
	g_signal_connect (app->window.menu.global.start_all, "activate",
	                  G_CALLBACK (main_app_on_global_start), app);
	g_signal_connect (app->window.menu.global.pause_all, "activate",
	                  G_CALLBACK (main_app_on_global_pause), app);
	g_signal_connect (app->window.menu.global.setting, "activate",
	                  G_CALLBACK (main_app_on_global_set), app);

	// View
	g_signal_connect (app->window.menu.view.columns, "activate",
	                  G_CALLBACK (main_app_on_view_column_set), app);
	g_signal_connect (app->window.menu.view.msg_area_items, "activate",
	                  G_CALLBACK (main_app_on_view_msg_item_set), app);

	// Help / About
	g_signal_connect (app->window.menu.help.about_urlgfe, "activate",
	                  G_CALLBACK (main_app_on_about), app);
}

void main_app_finalize (MainApp* app)
{
	main_window_finalize (&app->window);
	g_tree_foreach (app->queue_ctrl_tree, queue_ctrl_tree_func, NULL);
	g_tree_destroy (app->queue_ctrl_tree);
	g_object_unref (app->model);
	root_node_unref (app->rnode);
}

void main_app_run (MainApp* app)
{
	gtk_widget_show (GTK_WIDGET (app->window.self));
	main_app_job_changed (app);
	main_window_view_apply (&app->window);
	main_app_set_queuing (app, TRUE);

	if (app->msg_server)
		msg_server_activate (app->msg_server);

	// set clipboard and queuing timer
	g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 500,
	                    (GSourceFunc)main_app_on_timer,
	                    app, NULL);
	g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 300000,
	                    (GSourceFunc)main_app_on_timer_save,
	                    app, NULL);
}

void main_app_refresh (MainApp* app)
{
	if (app->window.queue_ctrl)
		gtk_widget_queue_draw (GTK_WIDGET (app->window.queue_ctrl->view));
	gtk_widget_queue_draw (GTK_WIDGET (app->window.category_view));
}

gboolean main_app_load_data (MainApp* app)
{
	ConfParser* parser;
	gboolean    return_val;

	parser = conf_parser_new ();
	conf_parser_push (parser, &root_node_parser, app->rnode);
	return_val = conf_parser_read_file (parser, urlgfe_path_data_file (), NULL);
	conf_parser_destroy (parser);

	return return_val;
}

gboolean main_app_save_data (MainApp* app)
{
	ConfWriter  writer;
	gboolean    return_val = FALSE;
	gchar*      conf_dir_utf8;

	conf_dir_utf8 = g_filename_to_utf8 (urlgfe_path_conf_dir (), -1, NULL, NULL, NULL);
	if ( ug_make_dirs (conf_dir_utf8, -1) ) {
		conf_writer_init (&writer, TRUE);
		return_val = conf_writer_open_file (&writer, urlgfe_path_data_file ());
		if (return_val)
			root_node_write_conf (app->rnode, &writer);
		conf_writer_finalize (&writer);
	}
	g_free (conf_dir_utf8);

	return return_val;
}

gboolean main_app_load_conf (MainApp* app)
{
	ConfParser* parser;
	gboolean    return_val;

	parser = conf_parser_new ();
	conf_parser_push (parser, &main_app_parser, app);
	return_val = conf_parser_read_file (parser, urlgfe_path_conf_file (), NULL);
	conf_parser_destroy (parser);

	return return_val;
}

gboolean main_app_save_conf (MainApp* app)
{
	ConfWriter  writer;
	gboolean    return_val = FALSE;
	gchar*      conf_dir_utf8;

	conf_dir_utf8 = g_filename_to_utf8 (urlgfe_path_conf_dir (), -1, NULL, NULL, NULL);
	if ( ug_make_dirs (conf_dir_utf8, -1) ) {
		conf_writer_init (&writer, TRUE);
		return_val = conf_writer_open_file (&writer, urlgfe_path_conf_file ());
		if (return_val)
			main_app_write_conf (app, &writer);
		conf_writer_finalize (&writer);
	}
	g_free (conf_dir_utf8);

	return return_val;
}

gboolean main_app_save_all  (MainApp* app)
{
	gboolean conf;
	gboolean data;

	conf = main_app_save_conf (app);
	data = main_app_save_data (app);

	return (conf && data);
}

void main_app_import_html (MainApp* app, const gchar* filename)
{
	FilterImporter* fim;
	HtmlImport*     html_import;
	UrlFilter*      filter_1;
	UrlFilter*      filter_2;
	UrlFilterClass* filter_class;
	GtkWidget* dialog;
	gboolean import_ok;
	gchar*   title;

	filter_1 = url_filter_new ();
	filter_class = url_filter_class_new (url_filter_class_address);
	url_filter_add_class (filter_1, filter_class);
	filter_class = url_filter_class_new (url_filter_class_extension);
	url_filter_add_class (filter_1, filter_class);

	filter_2 = url_filter_new ();
	filter_class = url_filter_class_new (url_filter_class_address);
	url_filter_add_class (filter_2, filter_class);
	filter_class = url_filter_class_new (url_filter_class_extension);
	url_filter_add_class (filter_2, filter_class);

	html_import = html_import_new ();
	html_import_add_element (html_import, filter_1, "A", "HREF");
	html_import_add_element (html_import, filter_2, "IMG", "SRC");
	import_ok = html_import_read_file (html_import, filename);
	html_import_free (html_import);

	if (import_ok) {
		title = g_strdup_printf (_("Import %s"), filename);
		fim = filter_importer_new (app->window.self, app);
		filter_importer_set_title (fim, title);
		g_free (title);

		filter_importer_add (fim, filter_1, _("Link <A>"));
		filter_importer_add (fim, filter_2, _("Image <IMG>"));
		filter_importer_activate (fim);
	} else {
		dialog = gtk_message_dialog_new (app->window.self,
		                                 GTK_DIALOG_DESTROY_WITH_PARENT,
		                                 GTK_MESSAGE_ERROR,
		                                 GTK_BUTTONS_CLOSE,
		                                 _("Import fail."));
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		url_filter_unref (filter_1);
		url_filter_unref (filter_2);
	}
}

void main_app_import_text (MainApp* app, const gchar* text, gboolean match_ext)
{
	FilterImporter* fim;
	UrlFilter*      filter;
	UrlFilterClass* filter_class;
	UrlInfo* uinfo;
	gchar*   line;
	int      line_len;
	int      n_url = 0;

	filter = url_filter_new ();
	filter_class = url_filter_class_new (url_filter_class_address);
	url_filter_add_class (filter, filter_class);
	filter_class = url_filter_class_new (url_filter_class_extension);
	url_filter_add_class (filter, filter_class);

	uinfo = g_malloc0 (sizeof (UrlInfo));
	do {
		text = text_get_line_beg (text, &line, &line_len);
		if (line == NULL || url_info_part_len (uinfo, line, line_len) == FALSE)
			continue;

		if (match_ext) {
			if (pcre_exec (app->pcre_regexp, NULL, uinfo->ext_filename,
			    uinfo->ext_filename_len, 0, PCRE_NO_UTF8_CHECK, NULL, 0) != 0)
			{
				continue;
			}
		}

		n_url++;
		url_filter_add_item_len (filter, line, line_len, NULL);
	} while (text);
	g_free (uinfo);

	fim = filter_importer_new (app->window.self, app);
	filter_importer_set_title (fim, _("Import from command line or clipboard"));
	filter_importer_add (fim, filter, "URL");
	filter_importer_activate (fim);
}

void main_app_import_url (MainApp* app, const gchar* filename)
{
	FILE* file;
	char* text;
	char* text_beg;
	char* text_line;

	FilterImporter* fim;
	UrlFilter*      filter;
	UrlFilterClass* filter_class;
	UrlInfo* uinfo;
	gchar*   title;

	file = ug_fopen (filename, "r");
	if (file == NULL)
		return;

	filter = url_filter_new ();
	filter_class = url_filter_class_new (url_filter_class_address);
	url_filter_add_class (filter, filter_class);
	filter_class = url_filter_class_new (url_filter_class_extension);
	url_filter_add_class (filter, filter_class);

	text = g_malloc (4096);
	uinfo = g_malloc0 (sizeof (UrlInfo));
	while ( fgets (text, 4096, file) ) {
		text_beg = text;
		// skip space character
		while (text_beg[0]==' ')
			text_beg++;
		// remove '\n'
		text_line = strchr (text_beg, '\n');
		if (text_line)
			text_line[0] = 0;

		if (text_beg[0] && url_info_part (uinfo, text_beg))
			url_filter_add_item (filter, text_beg, NULL);
	}
	g_free (uinfo);
	g_free (text);
	fclose (file);

	title = g_strdup_printf (_("Import %s"), filename);

	fim = filter_importer_new (app->window.self, app);
	filter_importer_set_title (fim, title);
	filter_importer_add (fim, filter, "URL");
	filter_importer_activate (fim);

	g_free (title);
}

gboolean main_app_export_url  (MainApp* app, const gchar* filename, QueueNode* qnode)
{
	DownloadNode* dnode;
	GtkWidget* dialog;
	FILE* file;

	file = ug_fopen (filename, "w");

	if (file == NULL) {
		dialog = gtk_message_dialog_new (app->window.self,
		                                 GTK_DIALOG_DESTROY_WITH_PARENT,
		                                 GTK_MESSAGE_ERROR,
		                                 GTK_BUTTONS_CLOSE,
		                                 _("Export fail.\nFile \"%s\" can't open."),
		                                 (filename) ? filename : "");
		gtk_dialog_run (GTK_DIALOG (dialog));
		gtk_widget_destroy (dialog);
		return FALSE;
	}

	for (dnode=queue_node_first_download (qnode); dnode; dnode=download_node_next (dnode)) {
		download_node_lock (dnode);
		if (dnode->url) {
			fputs (dnode->url, file);
			fputc ('\n', file);
		}
		download_node_unlock (dnode);
	}
	fclose (file);
	return TRUE;
}

GList*  main_app_get_selected_download (MainApp* app)
{
	GList* list = NULL;
	GtkTreeSelection* selection;

	if (app->window.queue_ctrl == NULL)
		return NULL;

	selection = gtk_tree_view_get_selection (app->window.queue_ctrl->view);
	gtk_tree_selection_selected_foreach (selection, selection_get_each, &list);
	return list;
}

void main_app_set_selected_download (MainApp* app, GList* list, int scrolled_mode)
{
	GtkTreeSelection* selection;
	GtkTreeView* tview;
	GtkTreeIter  iter;
	GtkTreePath* path;
	int  index;

	if (app->window.queue_ctrl == NULL)
		return;

	tview = app->window.queue_ctrl->view;
	selection = gtk_tree_view_get_selection (tview);
	// get stamp and initial GtkTreeIter
	gtk_tree_model_get_iter_first (app->window.queue_ctrl->model, &iter);
	while (list) {
		iter.user_data = list->data;
		gtk_tree_selection_select_iter (selection, &iter);
		if (scrolled_mode == 1 && list->next == NULL) {
			index = queue_node_download_position (app->window.queue_ctrl->qnode,
			                                      list->data);
			path = gtk_tree_path_new_from_indices (index, -1);
			gtk_tree_view_scroll_to_cell (tview, path, NULL, FALSE, 0.0, 0.0);
			gtk_tree_path_free (path);
		}
		if (scrolled_mode == -1 && list->prev == NULL) {
			index = queue_node_download_position (app->window.queue_ctrl->qnode,
			                                      list->data);
			path = gtk_tree_path_new_from_indices (index, -1);
			gtk_tree_view_scroll_to_cell (tview, path, NULL, FALSE, 0.0, 0.0);
			gtk_tree_path_free (path);
		}
		list = list->next;
	}
}

void main_app_remove_queue_ctrl (MainApp* app, QueueNode* qnode)
{
	QueueCtrl* qctrl;

	if (qnode == NULL)
		return;

	qctrl = g_tree_lookup (app->queue_ctrl_tree, qnode);
	if (qctrl) {
		g_tree_remove (app->queue_ctrl_tree, qnode);
		queue_ctrl_unref (qctrl);
	}
}

void main_app_set_queuing (MainApp* app, gboolean queuing)
{
	if (queuing) {
		app->queuing = TRUE;
		main_window_set_queue_state (&app->window, TRUE);
		main_app_job_changed (app);
	} else {
		app->queuing = FALSE;
		main_window_set_queue_state (&app->window, FALSE);
	}
}

void main_app_job_changed (MainApp* app)
{
	app->queuing_job = TRUE;
}

gboolean main_app_monitor_clipboard (MainApp* app, gboolean monitor)
{
	gboolean old_setting = app->clipboard_monitor;

	app->clipboard_monitor = monitor;
	return old_setting;
}

// config writer & parser --------------------------------------------
void main_app_write_conf (MainApp* app, ConfWriter* cw)
{
	GList* list = directory_history_get ();

	conf_writer_start_element (cw, URLGFE_CONF_TAG);

	conf_writer_start_element (cw, "clipboard");
	// pattern
	conf_writer_start_element (cw, "pattern");
	conf_writer_text (cw, app->clipboard_pattern);
	conf_writer_end_element (cw, "pattern");
	if (app->clipboard_monitor) {
		conf_writer_start_element (cw, "monitoring");
		conf_writer_end_element (cw, "monitoring");
	}
	conf_writer_end_element (cw, "clipboard");

	for (list=g_list_last (list); list; list=list->prev) {
		conf_writer_start_element (cw, "directory");
		conf_writer_text (cw, list->data);
		conf_writer_end_element (cw, "directory");
	}

	view_setting_write_conf (&app->window.view_setting, cw);
	conf_writer_end_element (cw, URLGFE_CONF_TAG);
}

// parser ---

// parser for clipboard ---
void main_app_clipboard_start_element (GMarkupParseContext* gmpc,
                                       const gchar*    element_name,
                                       const gchar**   attr_names,
                                       const gchar**   attr_values,
                                       gpointer        cparser,
                                       GError**        error)
{
	MainApp*  app = CONF_PARSER_DATA (cparser);

	if (strcmp (element_name, "pattern")==0)
		conf_parser_push_text_func_no_crlf (cparser, &app->clipboard_pattern);
	else if (strcmp (element_name, "monitoring")==0)
		app->clipboard_monitor = TRUE;
}

void main_app_clipboard_end_element (GMarkupParseContext*  gmpc,
                                     const gchar*      element_name,
                                     gpointer          cparser,
                                     GError**          error)
{
	if (strcmp (element_name, "clipboard")==0)
		conf_parser_pop (cparser);
	else if (strcmp (element_name, "pattern")==0)
		conf_parser_pop_text_func (cparser);
}

const GMarkupParser main_app_clipboard_parser = {
	main_app_clipboard_start_element,
	main_app_clipboard_end_element,
	NULL,
	NULL,
	NULL
};


// parser toplevel
void main_app_parser_text_directory (GMarkupParseContext*  gmpc,
                                     const gchar*          text,
                                     gsize                 text_len,
                                     gpointer              cparser,
                                     GError**              error)
{
	gchar* dir;

	dir = strndup_no_crlf (text, text_len);
	directory_history_set (dir);
	g_free (dir);
}

void main_app_parser_start_element (GMarkupParseContext* gmpc,
                                    const gchar*    element_name,
                                    const gchar**   attr_names,
                                    const gchar**   attr_values,
                                    gpointer        cparser,
                                    GError**        error)
{
	MainApp*  app = CONF_PARSER_DATA (cparser);

	if (strcmp (element_name, "directory")==0)
		conf_parser_set_text_func (cparser, main_app_parser_text_directory);
	else if (strcmp (element_name, "view_setting")==0)
		conf_parser_push (cparser, &view_setting_parser, &app->window.view_setting);
	else if (strcmp (element_name, "clipboard")==0)
		conf_parser_push (cparser, &main_app_clipboard_parser, app);
}

void main_app_parser_end_element (GMarkupParseContext*  gmpc,
                                  const gchar*      element_name,
                                  gpointer          cparser,
                                  GError**          error)
{
	if (strcmp (element_name, URLGFE_CONF_TAG)==0)
		conf_parser_pop (cparser);
	else if (strcmp (element_name, "directory")==0)
		conf_parser_set_text_func (cparser, NULL);

}

const GMarkupParser main_app_parser = {
	main_app_parser_start_element,
	main_app_parser_end_element,
	NULL,
	NULL,
	NULL
};

